/*
---
script: tornLog.js

description:     MooTools powered heavily customizable UI based logging class.

authors:
  - fyrye (http://torntech.com)
  
copyright: Copyright (c) 2010-2011 [William Baumbach](http://torntech.com/)

license:
  - MIT-style license

requires:
  core/1.2.4:
     - Class.Extras
     - Element.Event
     - Element.Style
     - Element.Dimensions
     - Selectors
     - Fx.Tween
  more/1.2.4.4:
    - Date
    - Drag.Move
    - HtmlTable.Zebra
    - HTMLTable.Sort

provides:
  - TornLog
...
*/
var TornLog = new Class({
        'version':      '1.1.1.0',                              // Major.Minor.UI.Revision
        'build':        '6f565dd037dfe40ab2bef70a178b1a69'
});

(function(){
/*
 * Protected variables EDIT AT YOUR OWN RISK
 */
        var own, opts, logged, rowID;
        var resizer, dragger;
        var UI = {
                tornLogUI: null,
                tornLogScroller: null,
                tornLogTable: null,
                tornLogMenu: null,
                tornLogDrag: null
        };

        var lineHeaders = {
                'num': {content: 'id', properties: {'class': 'num'}},
                'tm': {content: 'time', properties: {'class': 'tm'}},
                'msg': {content: 'msg', properties: {'class': 'msg'}},
                'dur': {content: 'dur', properties: {'class': 'dur'}}
        };

        var buttons = {
                'status' : new Element('div', {'class': 'button statusE', 'title': 'Disable', events: {'click': function(){own.toggleStatus(this);}}}),
                'clrLog' : new Element('div', {'class': 'button clr', 'title': 'Clear Log', events: {'click': function(){own.clearLog();}}}),
                'killUI' : new Element('div', {'class': 'button ui kill', 'title': 'Close', events: {'click': function(){own.killUI();}}}),
                'hideUI' : new Element('div', {'class': 'button ui hide', 'title': 'Hide', events: {'click': function(){own.toggleUI(this);}}}),
                'lockUI' : new Element('div', {'class': 'button ui lock', 'title': 'Lock', events: {'click': function(){own.toggleLockUI(this);}}})
        };

/*
 * Start of TornLog Class
 */
TornLog.implement({
        logged: [],                                                     // Stored messages as an Array

        Implements: [Events, Options, Chain],

        options:{
                // Log settings
                useMooLog: true,                                // Enables Usage of MooTools Log
                enabled: true,                                  // true: enables Log on init, false: myObject.enableLog() must be called to enable logging.

                // Log UI Options
                ui:{
                        enabled: true,                          // true: draws a UI in the browser, false: disables the UI from being drawn
                        placement: 'top',                       // Order in which to inject the UI ('top', 'bottom', 'before', 'after')
                        style: 'top:1px;right:1px;',// Custom style to load that may not work in css such as right/top/bottom
                        movable: true,                          // Enable drag to move the UI
                        sizable: true,                          // Enable drag resizing the UI,
                        opacity: 1,                                     // opacity of the UI - 1: fully visible, 0.x: partially visible, 0: not visible
                        clasName: 'tornLog',            // ID name to used to appened to the UI elements
                        wrapper: 'div',                         // Acceptable block elements EG: p, div, pre, code
                        container: false,                       // Container for Log UI
                        lockoriginalsize: true,         // Forces the minimal size to be the original size
                        keepincontainer: true,          // Forces the UI to remain inside of the container. (Disabled in IE)
                        minSize: {                                      // Minimal Size the UI can be resized to
                                x: 150,
                                y: 100
                        }
                        /*                      Possibly added on a later version
                        buttons:{
                                'status': true,                 // Display toggle Log Stauts button
                                'clrLog': true,                 // Display Clear Log button
                                'killUI': true,                 // Display Close UI button (destroys the UI)
                                'hideUI': true,                 // Display toggle Hide UI button
                                'lockUI': true                  // Display toggle Lock UI position button
                        }
                        */
                },

                // Log Line Options
                line: {
                        /*                      Possibly added on a later version
                        container: 'tr',                        // Container for each line
                        sects: 'td',                            // Container for each section of a line
                        headerContainer: 'tr',          // Container for the line header
                        headerSects: 'th',                      // Container for each section of the line header
                        */
                        start: 1,                                       // Starting Log Number
                        numbers: true,                          // Show Line Numbers
                        dates: true,                            // Show line Dates
                        dateFormat: '[%m-%d-%y %H:%M:%S]',      // Date/time format
                        durations: true,                        // Show Line durations in miliseconds
                        append: '',                                     // (string) append to message EG: messgage(string) | Example: message - tornLog
                        prepend: ''                                     // (string) to prepend to message EG: (string)message | Example: tornLog: message
                },

                // Log Events
                onInit: $empty,                                 // Function to fire when TornLog Starts
                onEnable: $empty,                               // Function to fire when logging is enabled
                onDisable: $empty,                              // Function to fire when logging is disabled
                onClearLog: $empty,                             // Function to fire when clearLog() is called
                onSendLog: $empty,                              // Function to fire when log() is called
                onShow: $empty,                                 // Function to fire when ui is shown
                onHide: $empty                                  // Function to fire when ui is hidden
        },

        isMooLog: !(typeof(Log) == 'undefined'),

        initialize: function(options){
                own = this;
                own.options.ui.container = document.body;
                own.setOptions(options);
                opts = own.options;
                logged = own.logged;

                own.fireEvent('init');

                rowID = opts.line.start;

                if (opts.useMooLog && own.isMooLog)
                        own.MooLog = new Log;

                opts.enabled?
                        own.enable():
                        own.disable();
        },

        addHeaders: function(){
                if (!$defined(UI.tornLogUI) || !own.created)
                        return own.createUI();

                var lineH = {
                        'num': opts.line.numbers,
                        'tm': opts.line.dates,
                        'msg': true,
                        'dur': opts.line.durations
                };

                var headersResult = [];
                $each(lineH, function(val, key){
                        if (val)
                                headersResult.push(lineHeaders[key]);
                });
                return headersResult;
        },

        addButtons: function(){
                var btns = function(){
                        var result = []
                        $each(buttons, function(val){
                                result.push(val);
                        });
                        return result;
                };
                UI.tornLogMenu.adopt(btns());
        },

        stopEvent: function(e){
                return e.stop();
        },

        createUI: function(){
                own.killUI();
                own.created = true;
                own.originalSize = false;

                UI.tornLogUI = new Element(opts.ui.wrapper, {
                        'id': opts.ui.clasName,
                        'class': 'wrapper',
                        'style': opts.ui.style
                });
/*
                                Didn't work for IE but worked great for FF and Safari
                UI.tornLogScrollWrapper = new Element('div', {
                        'id': opts.ui.clasName + 'scrollWrapper'
                });
*/
                UI.tornLogScroller = new Element('div', {
                        'class' : 'scroller'
                });

                UI.tornLogTable = new HtmlTable({
                        properties:{
                                'id': opts.ui.clasName + 'Table',
                                border: 0,
                                cellspacing: 0,
                                cellpadding: 1,
                                'class': 'log'
                        },
                        headers: own.addHeaders()
                });

                UI.tornLogMenu = new Element('div', {
                        'id': opts.ui.clasName + 'Menu'
                });

                UI.tornLogDrag = new Element('div', {
                        'id': opts.ui.clasName + 'Drag',
                        events: {
                                'mousedown': function(e){
                                        e.stopPropagation();
                                        UI.tornLogUI.addEvent('selectstart', own.stopEvent);
                                },
                                'mouseup': function(e){
                                        UI.tornLogUI.removeEvent('selectstart', own.stopEvent);
                                },
                                'contextmenu': own.stopEvent
                        }
                });
                own.addHeaders();
                own.addButtons();
                own.insertUI();
                own.toggleLockUI();
        },

        getContainer: function(){
                return opts.ui.keepincontainer?
                                        Browser.Engine.trident?
                                                false:
                                                opts.ui.container:
                                        false;
        },

        getOriginalSize: function(el){
                return own.originalSize = !own.originalSize?el.getSize():own.originalSize;
        },

        getScrollerSize: function(){
                return own.scrollOrigSize = !own.scrollOrigSize?UI.tornLogScroller.getSize().y:own.scrollOrigSize;
        },

        toggleStatus: function(el){
                if(!el) el = $$('#'+opts.ui.clasName+' .statusE') || $$('#'+opts.ui.clasName+' .statusD');
                if (el.hasClass('statusE')){
                        own.disable();
                        el.removeClass('statusE');
                        el.addClass('statusD');
                        el.setAttribute('title', 'Enable');
                } else {
                        own.enable();
                        el.removeClass('statusD');
                        el.addClass('statusE');
                        el.setAttribute('title', 'Disable');
                }
        },

        toggleLockUI: function(el){
                if(!el) el = $$('#'+opts.ui.clasName+' .lock') || $$('#'+opts.ui.clasName+' .unlock');
                if (opts.ui.movable){
                        if($chk(el)){
                                el.removeClass('unlock');
                                el.addClass('lock');
                                el.set('title', 'Lock');
                                UI.tornLogMenu.setStyle('cursor', 'move');
                                UI.tornLogDrag.setStyle('cursor', 'se-resize');
                        }
                        dragger.attach();
                        resizer.attach();
                } else {
                        if($chk(el)){
                                el.removeClass('lock');
                                el.addClass('unlock');
                                el.set('title', 'Unlock');
                                UI.tornLogMenu.setStyle('cursor', 'default');
                                UI.tornLogDrag.setStyle('cursor', 'default');
                        }
                        dragger.detach();
                        resizer.detach();
                }
                return opts.ui.movable = !opts.ui.movable;
        },

        lockSize: function(el){
                var minSize = opts.ui.lockoriginalsize?own.getOriginalSize(el):opts.ui.minSize;
                if (el.getSize().y < minSize.y)
                        el.setStyle('height', minSize.y);
                if (el.getSize().x < minSize.x)
                        el.setStyle('width', minSize.x);
        },

        addSizer: function(){
                own.addDragger();
                own.lockSize(UI.tornLogUI);
        },

        addDragger: function(){
                UI.tornLogScroller.addEvent('selectstart', own.stopEvent);
                UI.tornLogScroller.setStyle('MozUserSelect', 'none');
        },

        addComplete: function(){
                UI.tornLogScroller.removeEvent('selectstart', own.stopEvent);
                UI.tornLogScroller.setStyle('MozUserSelect', 'text');
        },

        insertUI: function(){
                //document.body
                UI.tornLogUI.inject(opts.ui.container, opts.ui.placement).adopt(
                        UI.tornLogMenu,
                                UI.tornLogScroller.adopt(
                                        UI.tornLogTable,
                                        UI.tornLogDrag
                                )
                );

                dragger = UI.tornLogUI.makeDraggable({
                        container: own.getContainer(),
                        handle: $(opts.ui.clasName + 'Menu'),
                        onDrag: own.addDragger,
                        onComplete: own.addComplete
                });

                resizer = UI.tornLogUI.makeResizable({
                        handle: UI.tornLogDrag,
                        onDrag: own.addSizer,
                        onComplete: own.addComplete
                });
                UI.tornLogUI.set('opacity', opts.ui.opacity);
        },

        killUI: function(){
                if ($chk($(opts.ui.clasName)))
                        $(opts.ui.clasName).dispose();

                $each(UI, function(_, key){
                                UI[key] = null;
                });
                own.originalSize = false;
                own.created = false;
        },

        showUI: function(){
                if (!$chk($(opts.ui.clasName))) return false;
                if(Fx.Tween){
                        UI.tornLogScroller.fade('in');
                } else
                        UI.tornLogScroller.setStyle('disply', '');
                return own.fireEvent('show');
        },

        hideUI: function(){
                if (!$chk($(opts.ui.clasName))) return false;
                if(Fx.Tween){
                        UI.tornLogScroller.fade('out');
                } else
                        UI.tornLogScroller.setStyle('display', 'none');
                return own.fireEvent('hide');
        },

        toggleUI: function(el){
                if(!own.uiHidden){
                        if($chk($(el))){
                                el.removeClass('hide');
                                el.addClass('show');
                                el.setAttribute('title', 'Show');
                        }
                        own.hideUI();
                } else {
                        if($chk($(el))){
                                el.removeClass('show');
                                el.addClass('hide');
                                el.setAttribute('title', 'Hide');
                        }
                        own.showUI();
                }
                return own.uiHidden = !own.uiHidden;
        },

        disable: function(killUI){
                own.fireEvent('disable');
                if (opts.useMooLog && own.isMooLog)
                        own.MooLog.disableLog();
                if (opts.ui.enabled && killUI)
                        own.killUI();

                own.send = own.disabled;
                own.send('Logging disabled');
        },

        enable: function(){
                own.fireEvent('enable');
                if (opts.useMooLog && own.isMooLog)
                        own.MooLog.enableLog();

                        if (opts.ui.enabled)
                                addEvent('domready', function(){own.createUI()});

                own.send = own.enabled;
                logged.each(function(args){
                        own.send.apply(own, args);
                });
                return own.clearLog(true);
        },

        disabled: function(){
                logged.push(arguments);
                return this;
        },

        enabled: function(msg, prefire, callback){
                own.curTime = own.getDate();
                own.fireEvent('sendLog');
                msg = opts.line.prepend + msg + opts.line.append;

                typeof(prefire) == 'function'?
                        own.preFire(msg, prefire):
                        own.addMsg(msg);

                if (typeof(callback) == 'function')
                        callback.run();
        },

        preFire: function(msg, prefire){
                return own.chain(
                        prefire.run(),
                        own.addMsg(msg)
                );
        },

        addMsg: function(msg){
                if (opts.useMooLog && own.isMooLog && msg)
                                own.MooLog.log(msg);
                if (!opts.ui.enabled || !$chk($(opts.ui.clasName))) return false;
                var cols = {
                        'num': {content: rowID++ + ')', properties: {'class': 'num'}},
                        'tm': {content: own.formatDate(), properties: {'class': 'tm'}},
                        'msg': {content: msg, properties: {'class': 'msg'}},
                        'dur': {content: own.duration(own.curTime) + ' ms', properties: {'class': 'dur'}}
                };

                var lineOpts = {
                        'num': opts.line.numbers,
                        'tm': opts.line.dates,
                        'msg': true,
                        'dur': opts.line.durations
                };

                var rows = [];
                $each(lineOpts, function(val, key){
                        if (val)
                                rows.push(cols[key])
                });

                UI.tornLogTable.push(rows, {'class':'line'});
                if (Fx.Scroll && !own.fxScroll)
                        own.fxScroll = new Fx.Scroll(UI.tornLogScroller);
                else if(Fx.Scroll)
                        own.fxScroll.toBottom();
        },

        clearLog: function(local){
                if (!local){
                        own.fireEvent('clearLog');
                        rowID = opts.line.start;
                        UI.tornLogTable.empty();
                }

                logged.empty();
                return own.send('Log initialized');
        },

        getDate: function(){
                return new Date();
        },

        duration: function(time){
                return !time?0:(own.getDate() - time);
        },

        formatDate: function(date){
                if (!date)
                        date = own.getDate();

                return date.format(opts.line.dateFormat);
        }
});
})();